// exception.cc 
//	Entry point into the Nachos kernel from user programs.
//	There are two kinds of things that can cause control to
//	transfer back to here from user code:
//
//	syscall -- The user code explicitly requests to call a procedure
//	in the Nachos kernel.  Right now, the only function we support is
//	"Halt".
//
//	exceptions -- The user code does something that the CPU can't handle.
//	For instance, accessing memory that doesn't exist, arithmetic errors,
//	etc.  
//
//	Interrupts (which can also cause control to transfer from user
//	code into the Nachos kernel) are handled elsewhere.
//
// For now, this only handles the Halt() system call.
// Everything else core dumps.
//
// Copyright (c) 1992-1993 The Regents of the University of California.
// All rights reserved.  See copyright.h for copyright notice and limitation 
// of liability and disclaimer of warranty provisions.

#include "copyright.h"
#include "system.h"
#include "syscall.h"
#include "table.h"
#include "addrspace.h"
#include <stdio.h>
#include <iostream>


using namespace std;

int copyin(unsigned int vaddr, int len, char *buf) {
    // Copy len bytes from the current thread's virtual address vaddr.
    // Return the number of bytes so read, or -1 if an error occors.
    // Errors can generally mean a bad virtual address was passed in.
    bool result;
    int n=0;			// The number of bytes copied in
    int *paddr = new int;

    while ( n >= 0 && n < len) {
      result = machine->ReadMem( vaddr, 1, paddr );
      while(!result) // FALL 09 CHANGES
	  {
   			result = machine->ReadMem( vaddr, 1, paddr ); // FALL 09 CHANGES: TO HANDLE PAGE FAULT IN THE ReadMem SYS CALL
	  }	
      
      buf[n++] = *paddr;
     
      if ( !result ) {
	//translation failed
	return -1;
      }

      vaddr++;
    }

    delete paddr;
    return len;
}

int copyout(unsigned int vaddr, int len, char *buf) {
    // Copy len bytes to the current thread's virtual address vaddr.
    // Return the number of bytes so written, or -1 if an error
    // occors.  Errors can generally mean a bad virtual address was
    // passed in.
    bool result;
    int n=0;			// The number of bytes copied in

    while ( n >= 0 && n < len) {
      // Note that we check every byte's address
      result = machine->WriteMem( vaddr, 1, (int)(buf[n++]) );

      if ( !result ) {
	//translation failed
	return -1;
      }

      vaddr++;
    }

    return n;
}

void Create_Syscall(unsigned int vaddr, int len) {
    // Create the file with the name in the user buffer pointed to by
    // vaddr.  The file name is at most MAXFILENAME chars long.  No
    // way to return errors, though...
    char *buf = new char[len+1];	// Kernel buffer to put the name in

    if (!buf) return;

    if( copyin(vaddr,len,buf) == -1 ) {
	printf("%s","Bad pointer passed to Create\n");
	delete buf;
	return;
    }

    buf[len]='\0';

    fileSystem->Create(buf,0);
    delete[] buf;
    return;
}

int Open_Syscall(unsigned int vaddr, int len) {
    // Open the file with the name in the user buffer pointed to by
    // vaddr.  The file name is at most MAXFILENAME chars long.  If
    // the file is opened successfully, it is put in the address
    // space's file table and an id returned that can find the file
    // later.  If there are any errors, -1 is returned.
    char *buf = new char[len+1];	// Kernel buffer to put the name in
    OpenFile *f;			// The new open file
    int id;				// The openfile id

    if (!buf) {
	printf("%s","Can't allocate kernel buffer in Open\n");
	return -1;
    }

    if( copyin(vaddr,len,buf) == -1 ) {
	printf("%s","Bad pointer passed to Open\n");
	delete[] buf;
	return -1;
    }

    buf[len]='\0';

    f = fileSystem->Open(buf);
    delete[] buf;

    if ( f ) {
	if ((id = currentThread->space->fileTable.Put(f)) == -1 )
	    delete f;
	return id;
    }
    else
	return -1;
}

void Write_Syscall(unsigned int vaddr, int len, int id) {
    // Write the buffer to the given disk file.  If ConsoleOutput is
    // the fileID, data goes to the synchronized console instead.  If
    // a Write arrives for the synchronized Console, and no such
    // console exists, create one. For disk files, the file is looked
    // up in the current address space's open file table and used as
    // the target of the write.
    
    char *buf;		// Kernel buffer for output
    OpenFile *f;	// Open file for output

    if ( id == ConsoleInput) return;
    
    if ( !(buf = new char[len]) ) {
		printf("%s","Error allocating kernel buffer for write!\n");
		return;
	} else {
		if ( copyin(vaddr,len,buf) == -1 ) {
			printf("%s","Bad pointer passed to to write: data not written\n");
			delete[] buf;
			return;
		}
    }

    if ( id == ConsoleOutput) {
      for (int ii=0; ii<len; ii++) {
	printf("%c",buf[ii]);
      }

    } else {
	if ( (f = (OpenFile *) currentThread->space->fileTable.Get(id)) ) {
	    f->Write(buf, len);
	} else {
	    printf("%s","Bad OpenFileId passed to Write\n");
	    len = -1;
	}
    }

    delete[] buf;
}

int Read_Syscall(unsigned int vaddr, int len, int id) {
    // Write the buffer to the given disk file.  If ConsoleOutput is
    // the fileID, data goes to the synchronized console instead.  If
    // a Write arrives for the synchronized Console, and no such
    // console exists, create one.    We reuse len as the number of bytes
    // read, which is an unnessecary savings of space.
    char *buf;		// Kernel buffer for input
    OpenFile *f;	// Open file for output

    if ( id == ConsoleOutput) return -1;
    
    if ( !(buf = new char[len]) ) {
	printf("%s","Error allocating kernel buffer in Read\n");
	return -1;
    }

    if ( id == ConsoleInput) {
      //Reading from the keyboard
      scanf("%s", buf);

      if ( copyout(vaddr, len, buf) == -1 ) {
	printf("%s","Bad pointer passed to Read: data not copied\n");
      }
    } else {
	if ( (f = (OpenFile *) currentThread->space->fileTable.Get(id)) ) {
	    len = f->Read(buf, len);
	    if ( len > 0 ) {
	        //Read something from the file. Put into user's address space
  	        if ( copyout(vaddr, len, buf) == -1 ) {
		    printf("%s","Bad pointer passed to Read: data not copied\n");
		}
	    }
	} else {
	    printf("%s","Bad OpenFileId passed to Read\n");
	    len = -1;
	}
    }

    delete[] buf;
    return len;
}

void Close_Syscall(int fd) {
    // Close the file associated with id fd.  No error reporting.
    OpenFile *f = (OpenFile *) currentThread->space->fileTable.Remove(fd);

    if ( f ) {
      delete f;
    } else {
      printf("%s","Tried to close an unopen file\n");
    }
}

void kernel_thread(int vaddr){
	machine->WriteRegister(PCReg, vaddr);
	machine->WriteRegister(NextPCReg, vaddr+4);
	machine->Run();
}

/* Run the executable, stored in the Nachos file "name", and return the 
 * address space identifier
 */
//SpaceId Exec_Syscall(int vaddr, int len) {
SpaceId Exec_Syscall(char* buf) {
	DEBUG('b', "%s: Exec syscall. %s\n", currentThread->getName(), buf);
	// Open the exe from file system
	OpenFile *executable;
    AddrSpace *space;
	Thread* t;
	/*char* buf;
    
    if ( !(buf = new char[len]) ) {
		printf("%s","Error allocating kernel buffer for write!\n");
		return -1;
	} else {
		if ( copyin(vaddr,len,buf) == -1 ) {
			printf("%s","Bad pointer passed to to write: data not written\n");
			delete[] buf;
			return -1;
		}
    }*/
	
	executable = fileSystem->Open(buf);
	
    if (executable == NULL) {
	printf("Unable to open file %s\n", buf);
	return -1;
    }
   
    // Create a new address space
	space = new AddrSpace(executable);

	t = new Thread("Main");
	t->space = space;
    
    delete executable;			// close file
	
	space->InitRegisters();
	space->RestorePageTable();		// load page table register
	
	// Update the process table
	int processID = processTable->Put(new SysProcess(space));
	
	// Put processID into address space
	space->SetProcessID(processID);
	
	// Fork a new thread for the main thread
	// Pass in the start address since this is the process main thread
	t->Fork(kernel_thread, 0);
	
	return processID;
}


/* This user program is done (status = 0 means exited normally). */
void Exit_Syscall(int status) {
	DEBUG('b', "%s: Exiting.\n", currentThread->getName());
	
	//if (last executing thread in the last process){
	if (processTable->NumFilled() == 1 &&
		((SysProcess*) processTable->Get(currentThread->space->processID))->numActiveThreads == 1) {
		interrupt->Halt();
	}
	
	DEBUG('b', "StackReg = %x.\n", machine->ReadRegister(StackReg));
	
	//if (not the last executing thread in the process, nor is it the 
		// last process){
	if (processTable->NumFilled() != 1 &&
		((SysProcess*) processTable->Get(currentThread->space->processID))->numActiveThreads != 1) {
		// -release the stack memory for this thread
		currentThread->space->ClearStack();
		// -only the pages where the valid bit is true
		// decrement thread count
		((SysProcess*) processTable->Get(currentThread->space->processID))->numActiveThreads--;
		
		currentThread->Finish();
	}
	
	//if (the last executing thread in a process, but not the last process)
	if (processTable->NumFilled() != 1 &&
		((SysProcess*) processTable->Get(currentThread->space->processID))->numActiveThreads == 1) {
		//-clear out all Locks/CVs for that process
		for (int i = 0; i < MAX_LOCKS; i++) {
			SysLock* sysLock = (SysLock*) lockTable->Get(i);
			if (sysLock && sysLock->processID == currentThread->space)
				lockTable->Remove(i);
		}
		for (int i = 0; i < MAX_CONDITIONS; i++) {
			SysCondition* sysCondition = (SysCondition*) conditionTable->Get(i);
			if (sysCondition && sysCondition->processID == currentThread->space)
				conditionTable->Remove(i);
		}
		//-clear out all valid entries in the page table
		processTable->Remove(currentThread->space->processID);
		delete currentThread->space;
		
		currentThread->Finish();
	}

	//temporary until Exit_Syscall finished
	//currentThread->Finish();
}

/* Fork a thread to run a procedure ("func") in the *same* address space 
 * as the current thread.
 */
void Fork_Syscall(unsigned int vaddr) {

	DEBUG('b', "%s: Fork_Syscall invoked. vaddr = %d.\n", currentThread->getName(), vaddr);
	//Must create a kernel thread
	Thread *t = new Thread("temp");
	
	//update the process table, add this thread to this process's list of threads
	//use this to increment process table currentThread->space->processID;
	//processTable[currentThread->space->processID]++;
	SysProcess* curProcess = ((SysProcess*) processTable->Get(currentThread->space->processID));
	
	curProcess->numActiveThreads++;
	// Link new thread to the current process address space.
	t->space = currentThread->space;
	t->space->InitRegisters(); // set up a stack for this thread
	//To have the thread execute code
	//t->Fork(Fork_Syscall, ((int) vaddr));
	t->Fork(kernel_thread, ((int) vaddr));
	
	//To have the thread execute code
	//t->Fork(kernel_thread(currentThread->space), vaddr);
}

/* Yield the CPU to another runnable thread, whether in this address space 
 * or not. 
 */
void Yield_Syscall() {
	currentThread->Yield();
}

void Acquire_Syscall(int index) {
	if (lockTable->Get(index) == 0){
		DEBUG('a',"ERROR: Lock at index %d is equal to null.\n",index);
		return;
	}
	
	DEBUG('b',"%s: Acquiring lock %d.\n", currentThread->getName(),index);
	((SysLock*) lockTable->Get(index))->lock->Acquire();
}

void Release_Syscall(int index) {
	if (lockTable->Get(index) == 0){
		DEBUG('a',"ERROR: Lock at index %d is equal to null.\n",index);
		return;
	}
	// Call release on the lock
	DEBUG('b',"%s: Releasing lock %d.\n", currentThread->getName(),index);
	((SysLock*) lockTable->Get(index))->lock->Release();
	// if the lock is "flagged to be deleted"
	if (((SysLock*) lockTable->Get(index))->flaggedForDelete){
		//if(lock is not in use){
			// If so delete the lock
			//lockTable->Remove(index);
		//}
	}
}

void Wait_Syscall(int conditionIndex, int lockIndex) {
	DEBUG('b',"Wait_Syscall invoked. condIndex=%d. lockIndex=%d.\n",conditionIndex,lockIndex);
	if (lockTable->Get(lockIndex) == 0){
		DEBUG('a',"ERROR: Lock at index %d is equal to null.\n",lockIndex);
		return;
	}
	if (conditionTable->Get(conditionIndex) == 0){
		DEBUG('a',"ERROR: Condition at index %d is equal to null.\n",conditionIndex);
		return;
	}
	// Call wait on the CV
	((SysCondition*) conditionTable->Get(conditionIndex))->condition->Wait(((SysLock*) lockTable->Get(lockIndex))->lock);
}

void Signal_Syscall(int conditionIndex, int lockIndex) {
	DEBUG('b',"Signal_Syscall invoked. condIndex=%d. lockIndex=%d.\n",conditionIndex,lockIndex);
	if (lockTable->Get(lockIndex) == 0){
		DEBUG('a',"ERROR: Lock at index %d is equal to null.\n",lockIndex);
		return;
	}
	if (conditionTable->Get(conditionIndex) == 0){
		DEBUG('a',"ERROR: Condition at index %d is equal to null.\n",conditionIndex);
		return;
	}
	// Signal the CV
	((SysCondition*) conditionTable->Get(conditionIndex))->condition->Signal(((SysLock*) lockTable->Get(lockIndex))->lock);
	// If the CV is "flagged to be deleted"
	if (((SysCondition*) conditionTable->Get(conditionIndex))->flaggedForDelete){
		// if(we are the last thread in it){
		//conditionTable->Remove(index);
		//}
	}
}

void Broadcast_Syscall(int conditionIndex, int lockIndex) {
	DEBUG('b',"Broadcast_Syscall invoked. condIndex=%d. lockIndex=%d.\n",conditionIndex,lockIndex);
	if (lockTable->Get(lockIndex) == 0){
		DEBUG('a',"ERROR: Lock at index %d is equal to null.\n",lockIndex);
		return;
	}
	if (conditionTable->Get(conditionIndex) == 0){
		DEBUG('a',"ERROR: Condition at index %d is equal to null.\n",conditionIndex);
		return;
	}
	// Broadcast to the CV
	((SysCondition*) conditionTable->Get(conditionIndex))->condition->Broadcast(((SysLock*) lockTable->Get(lockIndex))->lock);
	// If the CV is "flagged to be deleted"
	if (((SysCondition*) conditionTable->Get(conditionIndex))->flaggedForDelete){
		// By broadcasting to it we know the CV will be empty
		// delete the CV
		conditionTable->Remove(conditionIndex);
	}	
}

int CreateLock_Syscall(int name, int length) {
	// Create a new lock object
	SysLock* newLock = new SysLock(new Lock("Lock"), currentThread->space);
	// Add it to the lock array
	int index = lockTable->Put(newLock);
	DEBUG('b',"Creating Lock, at index %d.\n", index);
	return index;
}

void DestroyLock_Syscall(int index) {
	// if the lock is in use
		// set "to be destroyed flag" on the lock
		// ((SysLock) lockTable->Get(index))->flaggedForDelete = true;
	// else
		// Delete this lock and null the pointer in the array
	DEBUG('b',"Destroying Lock, at index %d.\n", index);
	lockTable->Remove(index);
	
}

int CreateCondition_Syscall(int name, int length) {
	// Create a new CV object
	SysCondition* newCondition = new SysCondition(new Condition("Condition"), currentThread->space);
	// Add it to the CV array
	int index = conditionTable->Put(newCondition);
	DEBUG('b',"Creating Condition, at index %d.\n", index);
	return index;
}

void DestroyCondition_Syscall(int index) {
	// if the CV is in use
		// set "to be destroyed flag" on the CV
		// ((SysCondition) conditionTable->Get(index))->flaggedForDelete = true;
	// else
		// Delete this CV and null the pointer in the array
	DEBUG('b',"Destroying Condition, at index %d.\n", index);
		conditionTable->Remove(index);
}

void ExceptionHandler(ExceptionType which) {
    int type = machine->ReadRegister(2); // Which syscall?
    int rv=0; 	// the return value from a syscall
	
	DebugInit("Lb");

    if ( which == SyscallException ) {
		switch (type) {
			default:
			DEBUG('a', "Unknown syscall - shutting down.\n");
			case SC_Halt:
			DEBUG('a', "Shutdown, initiated by user program.\n");
			interrupt->Halt();
			break;
			case SC_Create:
			DEBUG('a', "Create syscall.\n");
			Create_Syscall(machine->ReadRegister(4), machine->ReadRegister(5));
			break;
			case SC_Open:
			DEBUG('a', "Open syscall.\n");
			rv = Open_Syscall(machine->ReadRegister(4), machine->ReadRegister(5));
			break;
			case SC_Write:
			DEBUG('a', "Write syscall.\n");
			Write_Syscall(machine->ReadRegister(4),
					  machine->ReadRegister(5),
					  machine->ReadRegister(6));
			break;
			case SC_Read:
			DEBUG('a', "Read syscall.\n");
			rv = Read_Syscall(machine->ReadRegister(4),
					  machine->ReadRegister(5),
					  machine->ReadRegister(6));
			break;
			case SC_Close:
			DEBUG('a', "Close syscall.\n");
			Close_Syscall(machine->ReadRegister(4));
			break;
			case SC_Exec:
			DEBUG('a', "Exec syscall.\n");
			//rv = Exec_Syscall(machine->ReadRegister(4), machine->ReadRegister(5));
			rv = Exec_Syscall(""); // TODO change this
			break;
			case SC_Exit:
			DEBUG('a', "Exit syscall.\n");
			Exit_Syscall(machine->ReadRegister(4));
			break;
			case SC_Fork:
			DEBUG('a', "Fork syscall.\n");
			Fork_Syscall(machine->ReadRegister(4));
			break;
			case SC_Yield:
			DEBUG('a', "Yield syscall.\n");
			Yield_Syscall();
			break;
			case SC_Acquire:
			DEBUG('a', "Acquire syscall.\n");
			Acquire_Syscall(machine->ReadRegister(4));
			break;
			case SC_Release:
			DEBUG('a', "Release syscall.\n");
			Release_Syscall(machine->ReadRegister(4));
			break;
			case SC_Wait:
			DEBUG('a', "Wait syscall.\n");
			Wait_Syscall(machine->ReadRegister(4),machine->ReadRegister(5));
			break;
			case SC_Signal:
			DEBUG('a', "Signal syscall.\n");
			Signal_Syscall(machine->ReadRegister(4),machine->ReadRegister(5));
			break;
			case SC_Broadcast:
			DEBUG('a', "Broadcast syscall.\n");
			Broadcast_Syscall(machine->ReadRegister(4),machine->ReadRegister(5));
			break;
			case SC_CreateLock:
			DEBUG('a', "CreateLock syscall.\n");
			rv = CreateLock_Syscall(machine->ReadRegister(4),machine->ReadRegister(5));
			break;
			case SC_DestroyLock:
			DEBUG('a', "DestroyLock syscall.\n");
			DestroyLock_Syscall(machine->ReadRegister(4));
			break;
			case SC_CreateCondition:
			DEBUG('a', "CreateCondition syscall.\n");
			rv = CreateCondition_Syscall(machine->ReadRegister(4),machine->ReadRegister(5));
			break;
			case SC_DestroyCondition:
			DEBUG('a', "DestroyCondition syscall.\n");
			DestroyCondition_Syscall(machine->ReadRegister(4));
			break;
		}

		// Put in the return value and increment the PC
		machine->WriteRegister(2,rv);
		machine->WriteRegister(PrevPCReg,machine->ReadRegister(PCReg));
		machine->WriteRegister(PCReg,machine->ReadRegister(NextPCReg));
		machine->WriteRegister(NextPCReg,machine->ReadRegister(PCReg)+4);
		return;
    } else {
      cout<<"Unexpected user mode exception - which:"<<which<<"  type:"<< type<<endl;
      interrupt->Halt();
    }
}
